package work.linruchang.util;

import cn.hutool.core.bean.BeanPath;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.stream.CollectorUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.thoughtworks.qdox.JavaProjectBuilder;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaField;
import com.thoughtworks.qdox.model.JavaSource;
import lombok.SneakyThrows;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 源码解析器
 *
 * @author LinRuChang
 * @version 1.0
 * @date 2022/10/08
 * @since 1.8
 **/
public class SourceParseUtil {


    public static enum SourceTypeEnum {
        STRING, FILE
    }


    /**
     * 解析Java源码
     *
     * @param sourceTypeEnum    {@code javaSourceContent}的类型
     * @param javaSourceContent 源码类型 字符串OR文件
     * @return
     */
    @SneakyThrows
    public static JavaSource readJavaSource(SourceTypeEnum sourceTypeEnum, Object javaSourceContent) {
        Assert.notNull(sourceTypeEnum);

        switch (sourceTypeEnum) {
            case STRING: {
                Assert.isTrue(javaSourceContent instanceof String);
                String javaSourceContent1 = Convert.toStr(javaSourceContent);
                if (StrUtil.isNotBlank(javaSourceContent1)) {
                    JavaProjectBuilder builder = new JavaProjectBuilder();
                    return builder.addSource(IoUtil.getReader(IoUtil.toStream(javaSourceContent1.getBytes()), StandardCharsets.UTF_8));
                }
                break;
            }
            case FILE: {
                Assert.isTrue(javaSourceContent instanceof File);
                File javaSourceContentFile = Convert.convert(File.class, javaSourceContent);
                if (FileUtil.exist(javaSourceContentFile) && FileUtil.isFile(javaSourceContentFile)) {
                    JavaProjectBuilder builder = new JavaProjectBuilder();
                    return builder.addSource(javaSourceContentFile);
                }
                break;
            }
        }
        return null;
    }

    /**
     * 获取java源码里面的包名
     *
     * @param javaSourceContent Java源码内容
     * @return
     */
    public static String getPackageName(String javaSourceContent) {
        return Optional.ofNullable(readJavaSource(SourceTypeEnum.STRING, javaSourceContent))
                .map(JavaSource::getPackageName)
                .orElse(null);
    }

    /**
     * 获取java源码里面的包名
     *
     * @param javaSourceContentFile Java源码内容文件
     * @return
     */
    public static String getPackageName(File javaSourceContentFile) {
        return Optional.ofNullable(readJavaSource(SourceTypeEnum.FILE, javaSourceContentFile))
                .map(JavaSource::getPackageName)
                .orElse(null);
    }


    /**
     * 获取JavaBean的字段信息
     *
     * @param sourceTypeEnum    {@code javaSourceContent}的类型
     * @param javaSourceContent Java源码内容
     * @return
     */
    public static List<JavaField> getFirstLevelFieldInfos(SourceTypeEnum sourceTypeEnum, Object javaSourceContent) {
        return Optional.ofNullable(readJavaSource(sourceTypeEnum, javaSourceContent))
                .map(JavaSource::getClasses)
                .orElse(CollUtil.newArrayList())
                .stream()
                .map(JavaClass::getFields)
                .flatMap(List::stream)
                .collect(Collectors.toList());
    }

    /**
     * 获取类的全限定名
     *
     * @param javaClass
     * @return
     */
    private static String getTypeFullyQualifiedName(JavaClass javaClass) {
        return Optional.ofNullable(javaClass)
                // .map(javaClassInfo -> StrUtil.join(StrUtil.DOT, ArrayUtil.removeBlank(new String[]{javaClassInfo.getPackageName(), javaClassInfo.getName()})))
                .map(javaClassInfo -> {
                    String typeFullyQualifiedName = javaClass.toString();
                    if(StrUtil.endWith(typeFullyQualifiedName,"[]")){
                        typeFullyQualifiedName = StrUtil.format("[L{};", StrUtil.removeSuffix(typeFullyQualifiedName,"[]"));
                    }
                    return typeFullyQualifiedName;
                })
                .orElse(null);
    }

    /**
     * 获取字段的全限定名
     *
     * @param javaField
     * @return
     */
    private static String getFieldTypeFullyQualifiedName(JavaField javaField) {
        return Optional.ofNullable(javaField)
                .map(JavaField::getType)
                .map(SourceParseUtil::getTypeFullyQualifiedName)
                .orElse(null);
    }


    /**
     * 获取字段信息
     *
     * @param javaSourceContent 源码内容
     * @return key字段名  value字段数据类型（全限定名）
     */
    public static Map<String, String> getFirstLevelFields(String javaSourceContent) {
        return getFirstLevelFieldInfos(SourceTypeEnum.STRING, javaSourceContent).stream()
                .collect(CollectorUtil.toMap(JavaField::getName, SourceParseUtil::getFieldTypeFullyQualifiedName, (v1, v2) -> v2));
    }

    /**
     * 获取字段信息
     *
     * @param javaSourceContentFile 源码内容文件
     * @return key字段名  value字段数据类型（全限定名）
     */
    public static Map<String, String> getFirstLevelFields(File javaSourceContentFile) {
        return getFirstLevelFieldInfos(SourceTypeEnum.FILE, javaSourceContentFile).stream()
                .collect(CollectorUtil.toMap(JavaField::getName, SourceParseUtil::getFieldTypeFullyQualifiedName, (type1, type2) -> type1));
    }

    /**
     * 获取字段信息
     *
     * @param javaSourceContent 源码内容文件
     * @return key字段名  value字段数据类型
     */
    public static Map<String, Class> getFirstLevelField2Types(String javaSourceContent) {
        return MapUtil.emptyIfNull(getFirstLevelFields(javaSourceContent)).entrySet()
                .stream()
                .collect(CollectorUtil.toMap(Map.Entry::getKey, mapEntry -> {
                    return EnhanceClassUtil.getType(mapEntry.getValue());
                }, (ftype1, ftype2) -> ftype2));

    }


    /**
     * 获取字段信息
     *
     * @param javaSourceContent 源码内容文件
     * @return key字段名  value字段数据类型
     */
    public static Map<String, Class> getFirstLevelField2Types(File javaSourceContent) {
        return MapUtil.emptyIfNull(getFirstLevelFields(javaSourceContent)).entrySet()
                .stream()
                .collect(CollectorUtil.toMap(Map.Entry::getKey, mapEntry -> {
                    return EnhanceClassUtil.getType(mapEntry.getValue());
                }, (ftype1, ftype2) -> ftype2));

    }

    /**
     * 获取当前类所有自定义类信息
     *
     * @param javaSourceContent
     * @return
     */
    public static Set<JavaClass> getCustomAllClass(SourceTypeEnum sourceTypeEnum, Object javaSourceContent) {
        Set<JavaClass> result = CollUtil.newHashSet();
        JavaSource javaSource = readJavaSource(sourceTypeEnum, javaSourceContent);
        if (javaSource != null) {
            JavaClass currentJavaClass = javaSource.getClasses().get(0);
            getCustomAllClass(currentJavaClass, result);
        }
        return result;
    }

    /**
     * 获取当前类所有自定义类信息
     *
     * @return
     */
    private static void getCustomAllClass(JavaClass currentJavaClass, Set<JavaClass> resultClass) {
        if (currentJavaClass == null) {
            return;
        }
        boolean addFlag = resultClass.add(currentJavaClass);
        if (addFlag) {
            currentJavaClass.getNestedClasses().stream()
                    .forEach(javaClass -> getCustomAllClass(javaClass, resultClass));
        }
    }

    /**
     * 是否含循环引用的Bean（即javaClass1中有字段是javaClass2数据类型，javaClass2中有字段是javaClass1类型）
     *
     * @param javaClass1
     * @param javaClass2
     * @return
     */
    public static boolean isCircularreferenceFlag(JavaClass javaClass1, JavaClass javaClass2) {
        if (ObjectUtil.hasNull(javaClass1, javaClass2)) {
            return false;
        }
        boolean fieldsFlag1 = javaClass1.getFields().stream()
                .anyMatch(javaField -> javaClass2.equals(javaField.getType()));

        boolean fieldsFlag2 = javaClass2.getFields().stream()
                .anyMatch(javaField -> javaClass1.equals(javaField.getType()));
        return fieldsFlag1 && fieldsFlag2;
    }

    /**
     * 获取当前类代码的Bean结构
     *
     * @param sourceTypeEnum 代码类型
     * @param javaSourceContent 代码
     * @return key字段名  value字段类型
     */
    public static Map<String, Object> getBeanFieldStructure(SourceTypeEnum sourceTypeEnum, Object javaSourceContent) {
        return Optional.ofNullable(readJavaSource(sourceTypeEnum, javaSourceContent))
                .map(javaSource -> {
                    JavaClass currentJavaClass = javaSource.getClasses().get(0);
                    return getBeanFieldStructure(currentJavaClass, null, MapUtil.newHashMap());
                }).orElse(MapUtil.empty());
    }


    /**
     * 获取当前类代码的Bean结构
     * @param javaClass 当前类
     * @param parentKeyName 父类
     * @param result 结构
     * @return key字段名  value字段类型
     */
    private static Map<String, Object> getBeanFieldStructure(JavaClass javaClass, String parentKeyName, Map<String, Object> result) {
        javaClass.getFields().stream().forEach(javaField -> {
            String nextParentKeyName = StrUtil.format("{}{}.", StrUtil.blankToDefault(parentKeyName, StrUtil.EMPTY), javaField.getName());
            JavaClass currentFieldType = javaField.getType();
            if (EnhanceClassUtil.isNotBeanFieldJavaType(getFieldTypeFullyQualifiedName(javaField))) {

                // 非普通类型字段
                if (isCircularreferenceFlag(javaClass, currentFieldType)) {
                    currentFieldType.getFields()
                            .stream()
                            .forEach(javaField1 -> {
                                String nextnextParentKeyName = StrUtil.format("{}{}.", nextParentKeyName, javaField1.getName());
                                // if(javaField1.getType().isInner()) {
                                if (EnhanceClassUtil.isNotBeanFieldJavaType(getFieldTypeFullyQualifiedName(javaField1))) {
                                    if (!javaField1.getType().equals(javaClass)) {
                                        getBeanFieldStructure(javaField1.getType(), nextnextParentKeyName, result);
                                    }
                                } else {
                                    String fieldTypeFullyQualifiedName = getFieldTypeFullyQualifiedName(javaField1);
                                    BeanPath.create(nextnextParentKeyName).set(result, EnhanceClassUtil.getType(fieldTypeFullyQualifiedName));
                                }
                            });
                    // 普通类型字段
                } else {
                    getBeanFieldStructure(currentFieldType, nextParentKeyName, result);
                }
            } else {
                String fieldTypeFullyQualifiedName = getFieldTypeFullyQualifiedName(javaField);
                Class type = EnhanceClassUtil.getType(fieldTypeFullyQualifiedName);
                BeanPath.create(nextParentKeyName).set(result, type);
            }
        });
        return result;
    }


}
